﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using Pdelvo.Minecraft.Network;
using Pdelvo.Minecraft.Protocol;
using Pdelvo.Minecraft.Protocol.Packets;
using Pdelvo.Minecraft.Protocol.Helper;

namespace Pdelvo.Minecraft.Examples
{
    public class ServerExample
    {
        public void Run()
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //Now we need to bind the socket to the port and ip address we want
            socket.Bind(new IPEndPoint(IPAddress.Any, 25565));
            //And we need to set it into the listening mode
            socket.Listen(10);

            //Generate Key-Pair for encryption
            var keyPair = Security.GenerateRSAKeyPair();

            while (true)
            {
                var remoteSocket = socket.Accept();
                //Because we want to work with streams we need to use the NetworkStream Wrapper class for our remoteSocket socket
                var networkStream = new NetworkStream(remoteSocket);
                //You cannot be sure that packets can be read fully. So we need the FullyReadStream.
                var fullyReadStream = new FullyReadStream(networkStream);
                //Because the minecraft network stuff uses big endian we need to translate it
                var bigEndianStream = new BigEndianStream(fullyReadStream);
                //Allright. This implementation can be used as a client or a server. because we want to speak with a minecraft client we want to use the ClientRemoteInterface class. The constructor need a bigEndianStream and a protocol version. The current(12w18a) version is 32. But this does not mean that just pre rlease clients can connect. everypacket to identificate the version will work. You will have o change the version later if you know it.
                var remoteInterface = new ClientRemoteInterface(bigEndianStream, 32);
                //Now we can read our first packet!
                var packet = remoteInterface.ReadPacket();
                //When a new client connects to a server two different packets can be received. A Handshake or a Server list ping. the server list ping is sent when the client want to display a entry in the server list. 
                if (packet is PlayerListPing)
                {
                    //here we need to kick the client with a specially formated DisconnectPacket. You an use a helper method to create such a packet
                    var disconnectPacket = ProtocolHelper.BuildMotDPacket("My Cool Server", 333, 666);
                    remoteInterface.SendPacket(disconnectPacket);
                    remoteSocket.Close();
                }
                else if (packet is HandshakeRequest)
                {
                    //First of all we need to cast the packet to read information from it
                    var handshake = (HandshakeRequest)packet;
                    int protocolVersion = handshake.ProtocolVersion; //This is the protocol version given in the handshake packet. before 12w17a the version was sent in another packet. So if the client use an older version this field will be zero.
                    if (handshake.ProtocolVersion != 0) // If the protocol version in the handshake is not 0 the new login is used (version >= 31)
                    {
                        //Check if the protocol version is in range
                        if (handshake.ProtocolVersion < ProtocolInformation.MinSupportedClientVersion)
                        {
                            //Client outdated

                            return;
                        }
                        if (handshake.ProtocolVersion > ProtocolInformation.MaxSupportedClientVersion)
                        {
                            //Server outdated
                            return;
                        }
                        //Send encryption packet containing the public key
                        remoteInterface.SendPacket(new EncryptionKeyRequest { ServerID = "-", PublicKey =  keyPair.getPublic().getEncoded()});
                        
                        //Sometimes a keep alive packet occures here, so we skip them
                        do
                        {
                            packet = remoteInterface.ReadPacket();
                        } while (packet is KeepAlive);

                        //Read the shared key from the client response
                        var encryptionKeyResponse = (EncryptionKeyResponse)packet;
                        byte[] encryptedKey = encryptionKeyResponse.SharedKey;
                        //Decrypt the key using the private key
                        byte[] decryptedKey = Pdelvo.Minecraft.Network.Security.RSADecrypt(encryptedKey, keyPair.getPrivate());
                        var clientKey = Pdelvo.Minecraft.Network.Security.GenerateRS4Key(decryptedKey);
                        //Send a empty encryption response packet to the client
                        remoteInterface.SendPacket(new EncryptionKeyResponse { SharedKey = new byte[0] });

                        //Now we need to set the base stream of the fully read stream to a rc4 stream, to encrypt and decrypt every following packet
                        remoteInterface.SwitchToRC4Mode(clientKey);
                    }
                    else
                    {
                        //If the Protocol Version in this packet is 0 the old login process must be used. First of all we set the protocol version to the highest one with the old login process
                        remoteInterface.EndPoint.Version = 30;
                        remoteInterface.SendPacket(new HandshakeResponse { Hash = "-" });
                    }
                    //Read the next packet(the login request)
                    packet = remoteInterface.ReadPacket();

                    var request = (LoginRequest)packet;
                    //Finally the real protocol version:
                    protocolVersion = handshake.ProtocolVersion == 0 ? request.ProtocolVersion : handshake.ProtocolVersion;
                    remoteInterface.EndPoint.Version = protocolVersion;

                    //Now we want to spawn the player and send a chat message. First of alle we must send him a Login Response packet
                    var loginResponse = new LoginResponse
                    {
                        Difficulty = 1,
                        Dimension = 0,
                        EntityID = 1,
                        MapSeed = 1,
                        MaxPlayers = 100,
                        ServerMode = 0,
                        WorldHeight = 100
                    };
                    remoteInterface.SendPacket(loginResponse);
                    remoteInterface.SendPacket(new PlayerPositionLookRequest());
                    remoteInterface.SendPacket(new ChatPacket { Message = "Happy Coding!" });
                    //A joining player will spawn and see this message. A time out will occour some seconds later
                }
            }
        }
    }
}
